package cn.js.sangu.unitStore.websocket;

import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.springframework.stereotype.Service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
import cn.js.sangu.unitStore.common.constant.SysInfo;
import cn.js.sangu.unitStore.common.exception.ServiceException;
import cn.js.sangu.unitStore.common.util.SpringContextHolder;
import cn.js.sangu.unitStore.dto.MsgDTO;
import cn.js.sangu.unitStore.entity.ChatRecord;
import cn.js.sangu.unitStore.entity.MessageBox;
import cn.js.sangu.unitStore.service.ChatRecordService;
import cn.js.sangu.unitStore.service.MessageBoxService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@ServerEndpoint("/ws/{uid}")
public class WebSocketService {

    // 用sid存储客户端Session信息
    public static Map<String, Session> clients = new ConcurrentHashMap<>();

    // 存储不同用户的客户端Session信息集合
    public static Map<String, Set<String>> connection = new ConcurrentHashMap<>();

    // SessionId 会话ID
    private String sid = null;

    // 建立连接的用户id
    private String uid;

    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) {
        this.sid = UUID.randomUUID().toString();
        this.uid = uid;
        clients.put(this.sid, session);
        // 简化写法：若集合中不存在uid的Value，则新建Value
        Set<String> clientSet = connection.computeIfAbsent(uid, k -> new HashSet<>());
        clientSet.add(this.sid);
        log.info("用户【{}】建立连接，【{}】连接开启", this.uid, this.sid);

        // 用户上线，查询未读离线消息并推送
        try {
            List<MessageBox> unreadMessages = SpringContextHolder.getBean(MessageBoxService.class)
                    .getUnreadMessages(uid);
            if (Objects.nonNull(unreadMessages) && !unreadMessages.isEmpty()) {
                log.info("向用户【{}】推送{}条未读离线消息", uid, unreadMessages.size());
                for (MessageBox message : unreadMessages) {
                    MsgDTO msgDTO = new MsgDTO();
                    msgDTO.setUid(uid);
                    msgDTO.setOppo(message.getFromUserId());
                    msgDTO.setType(message.getType());
                    msgDTO.setContent(message.getContent());
                    msgDTO.setId(message.getId());
                    msgDTO.setIsRead(message.getIsRead());
                    // 发送消息到前端
                    session.getBasicRemote().sendText(JSONUtil.toJsonStr(msgDTO));
                    // 标记为已读
                    SpringContextHolder.getBean(MessageBoxService.class).markAsRead(message.getId());
                }
            }
        } catch (Exception e) {
            log.error("推送离线消息失败，用户ID：{}", uid, e);
        }
    }

    @OnClose
    public void onClose() {
        clients.remove(this.sid);
        // 清理connection集合中的相关条目
        Set<String> clientSet = connection.get(this.uid);
        if (clientSet != null) {
            clientSet.remove(this.sid);
            // 如果用户没有其他连接，则从connection中移除
            if (clientSet.isEmpty()) {
                connection.remove(this.uid);
            }
        }
        log.info("用户【{}】连接断开，【{}】连接关闭", this.uid, this.sid);
    }

    /**
     * 发送刷新通知给指定用户
     * 
     * @param receiverId 接收者ID
     * @param recordId   消息记录ID
     */
    public void sendRefreshNotification(String receiverId, Long recordId) {
        try {
            MsgDTO msgDTO = new MsgDTO();
            msgDTO.setType("refresh");
            msgDTO.setContent("消息状态已更新，请刷新聊天记录");
            msgDTO.setId(recordId);
            msgDTO.setMsgTime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));

            // 发送消息给接收者
            sendMessageToUser(receiverId, JSONUtil.toJsonStr(msgDTO));
            log.info("已向用户【{}】发送消息【{}】刷新通知", receiverId, recordId);
        } catch (Exception e) {
            log.error("发送刷新通知失败，用户ID：{}，消息ID：{}", receiverId, recordId, e);
        }
    }

    /**
     * 发送消息给指定用户
     * 
     * @param uid     用户ID
     * @param message 消息内容
     */
    public void sendMessageToUser(String uid, String message) {
        Set<String> clientSet = connection.get(uid);
        if (clientSet != null && !clientSet.isEmpty()) {
            for (String sid : clientSet) {
                Session session = clients.get(sid);
                if (session != null && session.isOpen()) {
                    try {
                        session.getBasicRemote().sendText(message);
                    } catch (IOException e) {
                        log.error("发送消息给用户【{}】失败", uid, e);
                    }
                }
            }
        }
    }

    @OnMessage
    public void onMessage(String content, Session session) {
        // log.info("收到用户【{}】的消息【{}】", this.uid, content);
        // 先判断是否为JSON格式消息
        if (!JSONUtil.isJsonObj(content)) {
            log.warn("接收到非JSON格式消息，内容：{}", content);
            sendMsg(new MsgDTO(this.uid, "warning", "建议使用JSON格式消息，非JSON消息将作为普通文本处理"));
            // 此处可添加普通文本消息业务处理逻辑（示例：直接回传消息）
            sendMsg(new MsgDTO(this.uid, "text", content));
            return;
        }
        try {
            MsgDTO msgDTO = JSONUtil.toBean(content, MsgDTO.class);
            String type = msgDTO.getType();
            if ("init".equals(type)) {
                msgDTO.setContent("连接成功，请选择会话人发送消息吧……");
                sendMsg(msgDTO);
            } else if ("read_ack".equals(type)) {
                try {
                    // 更新消息已读状态
                    Long msgId = msgDTO.getId();
                    if (Objects.nonNull(msgId)) {
                        ChatRecordService chatRecordService = SpringContextHolder.getBean(ChatRecordService.class);
                        chatRecordService.updateReadStatus(msgId, SysInfo.ReadingStatusIsConf);
                        // 转发已读通知给对方
                        sendMsg(msgDTO);
                    }
                } catch (Exception e) {
                    log.error("更新消息已读状态失败，消息ID：{}", msgDTO.getId(), e);
                }
            } else if ("chat".equals(type) || "system".equals(type)) {
                try {
                    // 保存聊天记录
                    ChatRecord chatRecord = new ChatRecord();
                    chatRecord.setUserId(msgDTO.getUid());
                    chatRecord.setOppoId(msgDTO.getOppo());
                    // 过滤表情符号 拦不住
                    // chatRecord.setContent(filterEmoji(msgDTO.getContent()));
                    chatRecord.setContent(msgDTO.getContent());
                    chatRecord.setType(type);
                    chatRecord.setIsRead(msgDTO.getIsRead());
                    // 保存聊天记录并获取生成的ID
                    ChatRecordService chatRecordService = SpringContextHolder.getBean(ChatRecordService.class);
                    chatRecordService.saveChatRecord(chatRecord);
                    // 设置消息ID为数据库生成的ID
                    log.info("数据库生成的消息ID：{}", chatRecord.getId());
                    msgDTO.setId(chatRecord.getId());
                } catch (Exception e) {
                    // log.error("保存聊天记录失败，消息内容：{}", msgDTO.getContent(), e);
                    log.error("保存聊天记录失败，消息内容：{}", msgDTO.getContent());
                    // 向客户端反馈保存失败
                    sendMsg(new MsgDTO(this.uid, "error", "消息发送失败，请联系林硕处理"));
                }
                sendChatMsg(msgDTO);
            }
            // else if ("system".equals(type)) {
            // sendMsgToOppo(msgDTO);
            // }
            else {
                throw new ServiceException("ws消息类型无匹配");
            }
        } catch (JSONException e) {
            log.error("消息解析失败，消息内容：{}，错误原因：{}", content, e.getMessage(), e); // 记录完整异常堆栈
            // 向客户端反馈格式错误（包含格式示例提示）
            sendMsg(new MsgDTO(this.uid, "error",
                    "消息格式不正确，请使用JSON格式，示例：{\"type\":\"xinTiao\",\"content\":\"heartbeat\"}"));
        }
    }

    @OnError
    public void onError(Throwable throwable) {
        // 区分处理EOFException，这通常是客户端正常关闭连接导致的
        if (throwable instanceof IOException && throwable.getMessage() != null
                && throwable.getMessage().contains("EOF")) {
            log.info("WS连接已被客户端关闭: {}", throwable.getMessage());
        } else {
            log.error("WS发生错误", throwable);
        }
    }

    /*
     * 发送聊天消息
     */
    public void sendChatMsg(MsgDTO mDto) {
        // 发给自己一份用于更新列表
        MsgDTO meDto = new MsgDTO();
        meDto.setId(mDto.getId());
        meDto.setUid(mDto.getUid());
        meDto.setOppo(mDto.getOppo());
        meDto.setType("chatMe");
        meDto.setContent(mDto.getContent());
        meDto.setMsgTime(mDto.getMsgTime());
        meDto.setFrom(mDto.getFrom());
        meDto.setTo(mDto.getTo());
        meDto.setIsRead(SysInfo.ReadingStatusUnconf); // 状态未确认
        sendMsg(meDto);
        // 消息发送给对方
        sendMsgToOppo(mDto);
    }

    /*
     * 发送消息给对方
     */
    public void sendMsgToOppo(MsgDTO mDto) {
        reversal(mDto);
        String targetUid = mDto.getUid();
        if (StrUtil.isEmpty(targetUid)) {
            log.error("发送消息失败：目标用户ID为空");
            return;
        }

        Set<String> clientSet = connection.get(targetUid);
        boolean isOnline = Objects.nonNull(clientSet) && !clientSet.isEmpty();

        // 创建消息箱对象
        MessageBox messageBox = createMessageBox(mDto, targetUid, isOnline);

        try {
            // 所有消息都先存入MessageBox信箱
            MessageBoxService messageBoxService = SpringContextHolder.getBean(MessageBoxService.class);
            MessageBox savedMessageBox = messageBoxService.saveOfflineMessage(messageBox);

            // 使用数据库返回的ID作为消息ID
            Long dbMessageId = savedMessageBox.getId();
            mDto.setId(dbMessageId);

            log.info("消息已存入MessageBox信箱，数据库ID：{}，目标用户：{}，消息内容：{}",
                    dbMessageId, targetUid, mDto.getContent());

            if (isOnline) {
                // 用户在线，使用数据库ID发送消息
                log.info("用户【{}】在线，准备发送消息，使用数据库消息ID：{}", targetUid, dbMessageId);
                log.info("用户【{}】的客户端连接集合：{}", targetUid, clientSet);
                sendMsg(mDto);
                log.info("消息已成功发送给用户【{}】，消息ID：{}", targetUid, dbMessageId);
            } else {
                log.info("用户【{}】不在线，消息已存储到信箱，消息ID：{}", targetUid, dbMessageId);
            }

        } catch (Exception e) {
            log.error("消息处理失败，用户ID：{}，消息内容：{}", targetUid, mDto.getContent(), e);
            // 仅在存储离线消息失败时抛出异常，在线发送失败已在sendMsg中处理
            if (!isOnline) {
                throw new ServiceException("保存离线消息失败: " + e.getMessage());
            }
        }
    }

    /**
     * 创建消息箱对象
     */
    private MessageBox createMessageBox(MsgDTO mDto, String targetUid, boolean isOnline) {
        MessageBox messageBox = new MessageBox();
        messageBox.setUserId(targetUid);
        messageBox.setFromUserId(mDto.getOppo());
        messageBox.setType(mDto.getType());
        messageBox.setTitle(isOnline ? mDto.getFrom() + "发来消息" : "你不在线时[" + mDto.getFrom() + "]发来消息，请及时查看");
        messageBox.setContent(mDto.getContent());
        messageBox.setMsgTime(DateUtil.now());
        messageBox.setIsRead(SysInfo.ReadingStatusUnconf); // 状态未确认
        messageBox.setRemark(isOnline ? "用户在线消息存储" : "用户离线消息存储");
        messageBox.setSessionId(this.sid);
        return messageBox;
    }

    /*
     * 发送消息
     */
    public void sendMsg(MsgDTO mDto) {
        String uid = mDto.getUid();
        mDto.setMsgTime(DateUtil.now());
        // mDto.setFrom();
        // mDto.setTo();
        if (StrUtil.isNotEmpty(uid)) {
            // 从连接取出该用户的Session集合
            Set<String> clientSet = connection.get(uid);
            // 判断用户目前是否存在客户端连接
            if (Objects.nonNull(clientSet)) {
                Iterator<String> it = clientSet.iterator();
                while (it.hasNext()) {
                    // 元素是SessionId
                    String sid = it.next();
                    Session session = clients.get(sid);
                    // 向每个会话发送消息
                    if (Objects.nonNull(session)) {
                        try {
                            // log.info("正式给用户【{}】发送消息【{}】", uid, mDto);
                            session.getBasicRemote().sendText(JSONUtil.toJsonStr(mDto));
                            // log.info("给用户【{}】发送消息【{}】",uid,mDto);
                        } catch (IOException e) {
                            throw new ServiceException(e.toString());
                        }
                    }
                }
            } else {
                log.error("用户【{}】不存在客户端连接，消息发送失败", uid);
            }
        } else {
            log.error("消息发送失败：没有获取到用户ID");
        }
    }

    /*
     * 聊天状态时身份角色互换
     */
    private void reversal(MsgDTO mDto) {
        // 取出对方ID
        String oppo = mDto.getOppo();
        // 取出自己ID
        String uid = mDto.getUid();
        if ("0".equals(oppo)) {
            // handshake握手消息
        } else {

        }
        if (StrUtil.isEmpty(uid)) {
            throw new ServiceException("聊天状态时身份角色互换失败：没有获取到用户ID");
        } else if (StrUtil.isEmpty(oppo)) {
            // throw new ServiceException("聊天状态时身份角色互换失败：没有获取到对方ID");
            oppo = uid;
        }
        // 两个ID都存在
        if (StrUtil.isNotEmpty(uid) && StrUtil.isNotEmpty(oppo)) {
            mDto.setUid(oppo);
            mDto.setOppo(uid);
        }
    }
}